﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Coding4Fun.Kinect.Wpf;
using DKE_Kinect.Entwicklung.Core;
using DKE_Kinect.Entwicklung.Kinect.GestureEvents;
using Microsoft.Research.Kinect.Nui;
using Microsoft.Xna.Framework;


namespace DKE_Kinect.Entwicklung.Kinect
{

    /// <summary>
    /// Manager für die Kinect und ihre Anbindung an das Projekt
    /// </summary>
    public class KinectManager : BaseManager
    {
        #region Fields

        /// <summary>
        /// Bool, der angibt, ob der Status ausgegeben werden soll
        /// </summary>
        private bool pShouldPrintState = false;

        /// <summary>
        /// Bool, der angibt, ob die Kalibrierung erfolgreich abgeschlossen wurde
        /// </summary>
        private bool pIsTracking = false;

        /// <summary>
        /// Bool, der angibt, ob die Kinect ordnungsgemäß läuft
        /// </summary>
        private bool pIsRunning = false;

        /// <summary>
        /// Aktueller Status dieser Kinect
        /// </summary>
        private KinectState pKinectState;

        Runtime pNui;

        private Dictionary<JointID, Vector3> pJoints = new Dictionary<JointID, Vector3>(20);

        private RightArmGestures pRightArmEvents;
        private LeftArmGestures pLeftArmEvents;
        private HandGestures pHandEvents;

        #endregion

        #region Properties

        /// <summary>
        /// Gibt zurück, ob die Kinect kalbriert wurde.
        /// </summary>
        /// <value>
        /// 	<c>true</c> wenn kalibriert; ansonsten, <c>false</c>.
        /// </value>
        /// 
        public bool IsTracking
        {
            get { return this.pIsTracking; }
        }

        /// <summary>
        /// Gibt zurück, ob die Kinect läuft.
        /// </summary>
        /// <value>
        /// 	<c>true</c> wenn sie läuft; ansonsten, <c>false</c>.
        /// </value>
        /// 
        public bool IsRunning
        {
            get { return this.pIsRunning; }
            set { this.pIsRunning = value; }
        }

        #endregion

        /// <summary>
        /// KinectSatus zurückgeben
        /// </summary>
        /// <value> KinectStatus </value>
        public KinectState KinectState
        {
            get
            {
                if (this.IsTracking)
                {
                    KinectState _curr = this.pKinectState;
                    this.pKinectState = new KinectState();
                    return _curr;
                }
                else return new KinectState();
            }
        }

        /// <summary>
        /// Konstruktor. Erstellt eine Instanz des Managers und weist die aktuelle Spielinstanz als Taktgeber zu.
        /// Initialisiert die Kinect.
        /// </summary>
        /// <param name="game">Aktuelle Spielinstanz</param>
        public KinectManager(Application game)
            : base(game)
        {


        }
        /// <summary>
        /// Initialisiert die Kinect. Dabei wird das entsprechende SAMPLE_XML_FILE geladen und die entsprechenden Einstellungen übergeben.
        /// Danach wird automatisch der ReaderThread zur Erkennung, Kalibrierung und Tracking des Spielers gestartet.
        /// Gestenerkennung soll über den entsprechenden EventHandler gehandlet werden.
        /// </summary>
        public void InitKinect()
        {
            pNui = new Runtime();

            pNui.Initialize(RuntimeOptions.UseSkeletalTracking);

            pIsRunning = true;

            pKinectState = new KinectState();
            pLeftArmEvents = new LeftArmGestures();
            pRightArmEvents = new RightArmGestures();
            pHandEvents = new HandGestures();

            //Parameter für Smoother, um Jitter zu verringern
            #region TransformSmooth

            pNui.SkeletonEngine.TransformSmooth = true;

            var parameters = new TransformSmoothParameters
            {
                Smoothing = 0.5f,
                Correction = 0.2f,
                Prediction = 0.04f,
                JitterRadius = 0.9f,
                MaxDeviationRadius = 0.9f
            };

            pNui.SkeletonEngine.SmoothParameters = parameters;

            #endregion

            //Handler für einzelne SkeletonFrames
            pNui.SkeletonFrameReady += new EventHandler<SkeletonFrameReadyEventArgs>(nui_SkeletonFrameReady);

            //Handler für die GestenEvents
            pLeftArmEvents.GestureEventHandler += new EventHandler<GestureEventArgs>(enqueueGesture);
            pRightArmEvents.GestureEventHandler += new EventHandler<GestureEventArgs>(enqueueGesture);
            pHandEvents.GestureEventHandler += new EventHandler<GestureEventArgs>(enqueueGesture);

        }


        /// <summary>
        /// Wird geworfen, wenn ein neuer SkeletonFrame erstellt wurde.
        /// Speichert die Koordinaten aller Punkte im Dicitionary
        /// </summary>
        /// <param name="src"></param>
        /// <param name="s"></param>
        private void nui_SkeletonFrameReady(object src, SkeletonFrameReadyEventArgs s)
        {
            SkeletonFrame skeletonFrame = s.SkeletonFrame;

            //Nimm das erste getrackte Skelett
            SkeletonData skeleton = (from _skels in skeletonFrame.Skeletons
                                     where _skels.TrackingState == SkeletonTrackingState.Tracked
                                     select _skels).FirstOrDefault();

            //Alle Körperpunkte im Dictionary speichern
            if (skeleton.TrackingState == SkeletonTrackingState.Tracked)
            {
                #region BodyParts

                GetJoint(skeleton.Joints[JointID.Head]);
                GetJoint(skeleton.Joints[JointID.ShoulderCenter]);
                GetJoint(skeleton.Joints[JointID.Spine]);
                GetJoint(skeleton.Joints[JointID.HipCenter]);

                GetJoint(skeleton.Joints[JointID.ShoulderLeft]);
                GetJoint(skeleton.Joints[JointID.ElbowLeft]);
                GetJoint(skeleton.Joints[JointID.HandLeft]);

                GetJoint(skeleton.Joints[JointID.ShoulderRight]);
                GetJoint(skeleton.Joints[JointID.ElbowRight]);
                GetJoint(skeleton.Joints[JointID.HandRight]);

                GetJoint(skeleton.Joints[JointID.HipLeft]);
                GetJoint(skeleton.Joints[JointID.HipRight]);

                #endregion

                pIsTracking = true;
            }
        }

        /// <summary>
        /// Erfragt einen SkeletonJoint des ersten getrackten Users und speichert ihn im im Dictionary.
        /// </summary>
        /// <param name="_joint">SkeletonJoint (z.B. Head, LeftArm etc.) -> siehe "public enum SkeletonJoint"</param>
        private void GetJoint(Joint _joint)
        {
            //Bei zu schlechter Qualität der Positionsbestimmung: Abbruch!
            if (_joint.Position.W < .6f) return;

            //X,Y = [-1,1] & Z = "Joint <-> in Metern" skalieren
            var scaledJoint = _joint.ScaleTo(640, 480, .9f, .9f);
            float Z = scaledJoint.Position.Z * 300;

            //Neue Position des Joints im Dicitionary speichern
            this.pJoints[_joint.ID] = new Vector3(scaledJoint.Position.X, scaledJoint.Position.Y, Z);
        }

        /// <summary>
        /// Updates Kinect Device.
        /// </summary>
        /// <param name="_gameTime"><see cref="GameTime"/>.</param>
        public override void Update(GameTime _gameTime)
        {
            if (this.pIsRunning)
            {

                if (this.pIsTracking)
                {
                    this.pKinectState.LeftHand = this.pJoints[JointID.HandLeft];
                    this.pKinectState.RightHand = this.pJoints[JointID.HandRight];

                    GestureEventListener();

                    if (this.pShouldPrintState)
                    {
                        Debug.WriteLine("LEFT HAND:" + this.pKinectState.LeftHand + " [" + _gameTime.TotalGameTime + "]");
                        Debug.WriteLine("RIGHT HAND:" + this.pKinectState.RightHand + " [" + _gameTime.TotalGameTime + "]");
                    }

                }

            }
        }

        /// <summary>
        /// Überwacht die GestenEvents eines bestimmten Users.
        /// </summary>
        public void GestureEventListener()
        {
            pLeftArmEvents.LookFor(this.pJoints[JointID.ShoulderLeft], this.pJoints[JointID.ElbowLeft], this.pJoints[JointID.HandLeft]);
            pRightArmEvents.LookFor(this.pJoints[JointID.ShoulderRight], this.pJoints[JointID.ElbowRight], this.pJoints[JointID.HandRight]);

            if (StateManager.CurrMode == eModus.mainmenu || StateManager.CurrMode == eModus.colormenu)
            {
                pHandEvents.LookFor(this.pJoints[JointID.HandRight], this.pJoints[JointID.ElbowRight], 0.4F);
            }
            else if (StateManager.CurrMode == eModus.quickmenu)
            {
                pHandEvents.LookFor(this.pJoints[JointID.HandRight], this.pJoints[JointID.ElbowLeft], 0.2F);
            }
        }

        /// <summary>
        /// Geste eines Events in den KinectState einreihen, sofern sie im aktuellen Modus zulässig ist.
        /// </summary>
        /// <param name="src">Objekt, von dem Event ausgeht</param>
        /// <param name="g">GestureEventArgs (Geste, Modus)</param>
        public void enqueueGesture(object src, GestureEventArgs g)
        {
            #region Unterscheidung der Gesten

            switch (g.Mode)
            {
                // Adden der Geste für Beenden der Kalibrierung -->
                // "Moduswechsel"
                case eModus.calibration:
                    switch (g.Gesture)
                    {
                        case Gestures.LeftHandUp:
                            this.pKinectState.LeftHandUp = true;
                            break;
                    }
                    break;

                // Adden der Gesten, die im Bewegungsmodus möglich sind -->
                // "Moduswechsel", "Menü öffnen", "Quickmenü öffnen"
                case eModus.moving:
                    switch (g.Gesture)
                    {
                        case Gestures.LeftHandUp:
                            this.pKinectState.LeftHandUp = true;
                            break;
                        case Gestures.LeftArmForward:
                            this.pKinectState.LeftArmForward = true;
                            break;
                        case Gestures.LeftArmOut:
                            this.pKinectState.LeftArmOut = true;
                            break;
                    }
                    break;

                // Adden der Gesten, die im Zeichnenmodus möglich sind -->
                // "Moduswechsel", "Menü öffnen", "Quickmenü öffnen"
                case eModus.painting:
                    switch (g.Gesture)
                    {

                        case Gestures.LeftHandUp:
                            this.pKinectState.LeftHandUp = true;
                            break;
                        case Gestures.LeftArmForward:
                            this.pKinectState.LeftArmForward = true;
                            break;
                        case Gestures.LeftArmOut:
                            this.pKinectState.LeftArmOut = true;
                            break;
                    }
                    break;

                // Adden der Gesten, die im Menümodus möglich sind -->
                // "Menüpunkt auswählen", "Menü schließen", Navigation
                case eModus.mainmenu:
                    switch (g.Gesture)
                    {
                        case Gestures.LeftArmOut:
                            this.pKinectState.LeftArmOut = true;
                            break;

                        case Gestures.LeftHandUp:
                            this.pKinectState.LeftHandUp = true;
                            break;

                        case Gestures.SwipeUp:
                            this.pKinectState.SwipeUp = true;
                            break;

                        case Gestures.SwipeDown:
                            this.pKinectState.SwipeDown = true;
                            break;

                        case Gestures.SwipeLeft:
                            this.pKinectState.SwipeLeft = true;
                            break;

                        case Gestures.SwipeRight:
                            this.pKinectState.SwipeRight = true;
                            break;
                    }
                    break;

                // Adden der Gesten, die im Quickmenümodus möglich sind -->
                // "Quickmenü schließen", Navigieren
                case eModus.quickmenu:
                    switch (g.Gesture)
                    {
                        case Gestures.LeftArmForward:
                            this.pKinectState.LeftArmForward = true;
                            break;

                        case Gestures.SwipeUp:
                            this.pKinectState.SwipeUp = true;
                            break;

                        case Gestures.SwipeDown:
                            this.pKinectState.SwipeDown = true;
                            break;

                        case Gestures.SwipeUpperLeft:
                            this.pKinectState.SwipeUpperLeft = true;
                            break;

                        case Gestures.SwipeUpperRight:
                            this.pKinectState.SwipeUpperRight = true;
                            break;

                        case Gestures.SwipeLowerLeft:
                            this.pKinectState.SwipeLowerLeft = true;
                            break;

                        case Gestures.SwipeLowerRight:
                            this.pKinectState.SwipeLowerRight = true;
                            break;

                    }
                    break;

                // Adden der Gesten, die im Kameramodus möglich sind -->
                // Kamerabewegung/-rotation starten
                case eModus.cameraModeOrbital:
                case eModus.cameraModeLook:
                    switch (g.Gesture)
                    {
                        case Gestures.LeftArmForward:
                            this.pKinectState.LeftArmForward = true;
                            break;

                        case Gestures.LeftArmOut:
                            this.pKinectState.LeftArmOut = true;
                            break;

                        case Gestures.LeftHandUp:
                            this.pKinectState.LeftHandUp = true;
                            break;
                    }
                    break;

                // Adden der Gesten, die im Radiergummimodus möglich sind -->
                // "Moduswechsel", "Menü öffnen", "Quickmenü öffnen"
                case eModus.eraser:
                    switch (g.Gesture)
                    {

                        case Gestures.LeftHandUp:
                            this.pKinectState.LeftHandUp = true;
                            break;
                        case Gestures.LeftArmForward:
                            this.pKinectState.LeftArmForward = true;
                            break;
                        case Gestures.LeftArmOut:
                            this.pKinectState.LeftArmOut = true;
                            break;
                    }
                    break;

                // Adden der Gesten, die im Farbwahlmodus möglich sind -->
                // "Menüpunkt auswählen", "Menü schließen", Navigation
                case eModus.colormenu:
                    switch (g.Gesture)
                    {
                        case Gestures.LeftArmOut:
                            this.pKinectState.LeftArmOut = true;
                            break;

                        case Gestures.LeftHandUp:
                            this.pKinectState.LeftHandUp = true;
                            break;

                        case Gestures.SwipeUp:
                            this.pKinectState.SwipeUp = true;
                            break;

                        case Gestures.SwipeDown:
                            this.pKinectState.SwipeDown = true;
                            break;

                        case Gestures.SwipeLeft:
                            this.pKinectState.SwipeLeft = true;
                            break;

                        case Gestures.SwipeRight:
                            this.pKinectState.SwipeRight = true;
                            break;
                    }
                    break;
            }
            #endregion
        }

        /// <summary>
        /// Draw Zyklus: Wird kontinuierlich getrigger von der Game Klasse. Dient zum Zeichnen des Screens
        /// </summary>
        /// <param name="gameTime">Spielzeit Objekt mit Zeit seit letzem Update, Gesamtlaufzeit u.Ä.</param>
        public override void Draw(Microsoft.Xna.Framework.GameTime gameTime)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Uninitialisiere die Kinect
        /// </summary>
        public void Uninit()
        {
            pNui.Uninitialize();
        }

    }
}
